package cn.snmp.mutual.org.opennms.protocols.snmp;

import cn.snmp.mutual.org.opennms.protocols.snmp.asn1.AsnDecodingException;
import cn.snmp.mutual.org.opennms.protocols.snmp.asn1.AsnEncoder;
import lombok.extern.slf4j.Slf4j;
import cn.snmp.mutual.uk.stack.DecodingException;
import cn.snmp.mutual.uk.stack.ListeningContext;

import java.net.*;
import java.io.*;

@Slf4j
class SnmpPortal {
    private SnmpPacketHandler m_handler;
    private DatagramSocket m_comm;
    private Thread m_recvThread;
    private AsnEncoder m_encoder;
    private volatile boolean m_isClosing;
    private boolean bSocketSoTimeoutRequired;
    private static final String PROP_SOCKET_TIMEOUT_REQUIRED = "org.opennms.joeSNMP.vmhacks.socketSoTimeoutRequired";
    private static final String PROP_SOCKET_TIMEOUT_PERIOD = "org.opennms.joeSNMP.vmhacks.socketSoTimeoutPeriod";

    private SnmpPortal() throws UnsupportedOperationException {
        this.bSocketSoTimeoutRequired = true;
        throw new UnsupportedOperationException("Illegal constructor call");
    }

    SnmpPortal(final SnmpPacketHandler handler, final AsnEncoder encoder, final int port) throws SocketException {
        this.bSocketSoTimeoutRequired = true;
        if (handler == null || encoder == null || port < 0) {
            throw new IllegalArgumentException("Invalid argument");
        }
        this.m_handler = handler;
        this.m_comm = new DatagramSocket(port);
        this.bSocketSoTimeoutRequired = true;
        final String strSocketSoTimeoutRequired = System.getProperty("org.opennms.joeSNMP.vmhacks.socketSoTimeoutRequired");
        if (strSocketSoTimeoutRequired != null && strSocketSoTimeoutRequired.equals("no")) {
            this.bSocketSoTimeoutRequired = false;
        }
        if (this.bSocketSoTimeoutRequired) {
            final String strSocketSoTimeoutPeriod = System.getProperty("org.opennms.joeSNMP.vmhacks.socketSoTimeoutPeriod");
            int timeout = 3000;
            if (strSocketSoTimeoutPeriod != null) {
                try {
                    timeout = Integer.parseInt(strSocketSoTimeoutPeriod);
                } catch (NumberFormatException e) {
                    timeout = 3000;
                }
            }
            this.m_comm.setSoTimeout(timeout);
        }
        this.m_isClosing = false;
        (this.m_recvThread = new Thread(new Receiver(), "faultm_snmpportal_trapreceiver")).setUncaughtExceptionHandler(new UncaughtExceptionHandlerImpl());
        this.m_encoder = encoder;
        this.m_recvThread.start();
        final Thread daemon = new Thread(new Runnable() {
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(3000L);
                    } catch (InterruptedException ex) {
                    }
                    if (SnmpPortal.this.m_isClosing) {
                        break;
                    }
                    if (!SnmpPortal.this.m_isClosing && !SnmpPortal.this.isRecvThreadAlive()) {
                        try {
                            SnmpPortal.this.reStart();
                            continue;
                        } catch (SocketException e) {
                            log.info("Failed to Restart trap receiver after shutdown");
                            log.info("" + e);
                        }
                        break;
                    }
                }
            }
        }, "faultm_snmpportal_trapreceiveprotector");
        daemon.start();
    }

    private boolean isRecvThreadAlive() {
        return this.m_recvThread.isAlive();
    }

    private void reStart() throws SocketException {
        log.info("Restart trap receiver after shutdown");
        (this.m_recvThread = new Thread(new Receiver(), "faultm_snmpportal_trapreceiver")).setUncaughtExceptionHandler(new UncaughtExceptionHandlerImpl());
        this.m_recvThread.start();
    }

    void handlePkt(final DatagramPacket pkt) throws SnmpPduEncodingException, AsnDecodingException {
        final byte[] buf = pkt.getData();
        int offset = 0;
        Object[] rVals = this.m_encoder.parseHeader(buf, offset);
        offset = (int) rVals[0];
        final byte asnType = (byte) rVals[1];
        final int asnLength = (int) rVals[2];
        if (asnType != 48) {
            throw new AsnDecodingException("Invalid SNMP ASN.1 type");
        }
        if (asnLength > pkt.getLength() - offset) {
            throw new SnmpPduEncodingException("Insufficent data in packet");
        }
        final SnmpInt32 int32 = new SnmpInt32();
        offset = int32.decodeASN(buf, offset, this.m_encoder);
        if (int32.getValue() != 0 && int32.getValue() != 1) {
            if (ContextPoolHandler.getInstance().getListeningContextPool() == null) {
                log.error(("$$$$$$$$$$$ ContextPoolHandler.getInstance().getListeningContextPool() is " + (Object) null));
            }
            final ListeningContext lc = ContextPoolHandler.getInstance().getListeningContextPool().getMatchingContext();
            try {
                final byte[] b = new byte[pkt.getLength()];
                System.arraycopy(buf, 0, b, 0, pkt.getLength());
                log.error("-------------------------------------------------------------------");
                log.error(("receive IP: " + pkt.getAddress().getHostAddress() + " v3 trap info " + b));
                lc.processIncomingMessage(pkt.getAddress().getHostAddress(), pkt.getPort(), b);
                return;
            } catch (DecodingException e) {
                e.printStackTrace();
            } catch (IOException e2) {
                e2.printStackTrace();
            }
        }
        final SnmpOctetString community = new SnmpOctetString();
        offset = community.decodeASN(buf, offset, this.m_encoder);
        rVals = this.m_encoder.parseHeader(buf, offset);
        final int cmd = (int) rVals[1] + 256;
        switch (cmd) {
            case 160:
            case 161:
            case 162:
            case 163:
            case 166:
            case 167:
            case 168: {
                final SnmpPduPacket pdu = new SnmpPduRequest();
                offset = pdu.decodeASN(buf, offset, this.m_encoder);
                this.m_handler.processSnmpMessage(pkt.getAddress(), pkt.getPort(), int32, community, cmd, pdu);
                break;
            }
            case 165: {
                final SnmpPduPacket pdu = new SnmpPduBulk();
                offset = pdu.decodeASN(buf, offset, this.m_encoder);
                this.m_handler.processSnmpMessage(pkt.getAddress(), pkt.getPort(), int32, community, cmd, pdu);
                break;
            }
            case 164: {
                final SnmpPduTrap trap = new SnmpPduTrap();
                offset = trap.decodeASN(buf, offset, this.m_encoder);
                this.m_handler.processSnmpTrap(pkt.getAddress(), pkt.getPort(), community, trap);
                break;
            }
            default: {
                throw new SnmpPduEncodingException("No matching PDU type found");
            }
        }
    }

    void send(final SnmpPeer peer, final byte[] buf, final int length) throws IOException {
        final DatagramPacket pkt = new DatagramPacket(buf, length, peer.getPeer(), peer.getPort());
        this.m_comm.send(pkt);
    }

    void send(final SnmpPeer peer, final byte[] buf) throws IOException {
        this.send(peer, buf, buf.length);
    }

    void setPacketHandler(final SnmpPacketHandler hdl) {
        if (hdl == null) {
            throw new IllegalArgumentException("The packet handler must not be null");
        }
        this.m_handler = hdl;
    }

    SnmpPacketHandler getPacketHandler() {
        return this.m_handler;
    }

    void setAsnEncoder(final AsnEncoder encoder) {
        if (encoder == null) {
            throw new IllegalArgumentException("The ASN.1 codec must not be null");
        }
        this.m_encoder = encoder;
    }

    AsnEncoder getAsnEncoder() {
        return this.m_encoder;
    }

    boolean isClosed() {
        return this.m_isClosing;
    }

    void close() {
        this.m_isClosing = true;
        this.m_comm.close();
        try {
            if (!this.m_recvThread.equals(Thread.currentThread())) {
                this.m_recvThread.join();
            }
        } catch (InterruptedException ex) {
        }
    }


    private class Receiver implements Runnable {
        private int setup() {
            int bufSz = 65536;
            try {
                SnmpPortal.this.m_comm.setReceiveBufferSize(bufSz);
            } catch (SocketException err) {
                bufSz = 0;
            }
            if (bufSz == 0) {
                try {
                    bufSz = SnmpPortal.this.m_comm.getReceiveBufferSize();
                } catch (SocketException err) {
                    bufSz = 0;
                }
            }
            return bufSz;
        }

        public void run() {
            log.info("Start trap receiver ");
            final int bufSz = this.setup();
            if (bufSz == 0) {
                return;
            }
            final byte[] buf = new byte[bufSz];
            final DatagramPacket pkt = new DatagramPacket(buf, bufSz);
            while (!SnmpPortal.this.m_isClosing) {
                try {
                    pkt.setLength(bufSz);
                    SnmpPortal.this.m_comm.receive(pkt);
                    SnmpPortal.this.handlePkt(pkt);
                } catch (SocketTimeoutException ste) {
                } catch (InterruptedIOException ioe) {
                    log.debug("" + ioe);
                } catch (SnmpPduEncodingException err) {
                    log.error("" + err);
                    SnmpPortal.this.m_handler.processBadDatagram(pkt);
                } catch (AsnDecodingException err2) {
                    log.error("" + err2);
                    SnmpPortal.this.m_handler.processBadDatagram(pkt);
                } catch (Exception e) {
                    log.error("" + e);
                    if (SnmpPortal.this.m_isClosing) {
                        continue;
                    }
                    SnmpPortal.this.m_handler.processException(e);
                }
            }
        }
    }

    private class UncaughtExceptionHandlerImpl implements Thread.UncaughtExceptionHandler {
        public void uncaughtException(final Thread t, final Throwable e) {
            log.info("" + e);
        }
    }
}
